ARM 存储器映射和 CMSIS
ARM 存储器映射
ARM Cortex-M 内核规定了一套统一的地址空间布局。对于 ARM CPU,其所有的内容都是地址。包括 Flash、RAM 和其他外设,都会对应到某一段地址空间。CPU 并不知道哪个地址是什么,只知道向某个地址写入或者读取。至于地址和设备的对应关系,就是 ARM 存储器映射。
对于 32 位的 Cortex-M,其地址宽度为 32 位。也就是说,CPU 最大可以访问的地址有 个,共 4GB,即从 0x00000000 到 0xffffffff 均可以访问。这个范围被叫做地址空间。这个空间中的不同区域被分配给不同硬件。
下图展示了 ARM 架构中系统内存映射的核心区域划分:

特别的,对于 Cortex-M 系列,其有:
| 地址范围 | 区域名称 | 描述 |
|---|---|---|
| 0x0000_0000~0x1FFF_FFFF | 代码区域(Code) | 用于存放程序代码,通常映射到芯片内部的 Flash 存储器。 |
| 0x2000_0000~0x3FFF_FFFF | SRAM 区域 | 用于存放数据(变量、堆栈等),通常映射到芯片内部的 SRAM |
| 0x4000_0000~0x5FFF_FFFF | 片上外设 | 映射到芯片厂商集成的外设,如 GPIO 等 |
| 0x6000_0000~0x9FFF_FFFF | 外部 RAM | 用于拓展外部存储器(SDRAM,SRAM 等) |
| 0xA000_0000~0xDFFF_FFFF | 外部设备 | 用于拓展外部设备(FPGA 等) |
| 0xE000_0000~0xE00F_FFFF | 私有外设总线(PPB) | 这部分包含了 Cortex-M 本身的外设: - NVIC (嵌套向量中断控制器) - SCB(系统控制块) - SysTick(系统定时器) - MPU(内存保护单元) |
特别的,对于 STM32,代码实际上存储在 0x0800_0000 位置。上表是 ARM 官方的规定,实际代码的存放位置是由厂商决定的。通过芯片厂商 ST 的硬件实现,0x0800_0000 位置的代码被重映射到 0x00_0000 上。
很多 MCU 都具有启动引脚(BOOT 0/1)。这些引脚在上电复位时的电平,决定了芯片将什么物理内存映射到启动地址 0x0000_0000。对于 STM32,其支持从主 Flash 启动(重定向到 0x0800_0000)、从程序存储器 BootROM 启动(ISP 模式,串口/USB 下载程序在 0x1FFF_0000)、从内置 SRAM 启动(0x2000_0000)等。
在 C 语言中一般用以下方式设置外设、读写内存:
*(volatile uint32_t*)0x40021018 = 0x1;
这本质是在向 0x40021018 发起一次总线写操作。如果这个地址是在 Flash 中,则是向 Flash 中写内容;如果是某个外设地址,就是在写寄存器。通过这种方式,ARM 实现了各种 CPU 操作的指令统一。
CMSIS
对于外设、内存的读写和设置都是通过对某个地址进行读写,这种方式虽然统一了硬件访问模型,但是通过裸地址来访问会存在一系列问题:
- 不同厂商命名风格不同
- 寄存器地址可读性差,难以维护
- 不同编译器和 SDK 之间缺乏统一接口
例如:
*(volatile uint32_t*)(0x40021000 + 0x18) |= (1 << 5);
很难能够看出是在操作哪个外设的什么寄存器。
因此,ARM 提出了 CMSIS(Cortex Microcontroller Software Interface Standard)规范,用于统一 Cortex-M 的软件接口。
CMSIS 本身并不负责设计硬件地址映射,而是在已有的 Memory Map 的基础上提供统一的软件描述方式。下图为其软件架构。
+-------------------------------------------------+
| 用户应用代码 | 中间件 (RTOS, 文件系统, UI, 网络) |
+-------------------------------------------------+
| CMSIS 层 |
| +-----------+ +------------+ +------+ +-------+ |
| | CMSIS | | CMSIS | | CMSIS| | CMSIS | |
| | Driver | | DSP | | RTOS| | NN | |
| +-----------+ +------------+ +------+ +-------+ |
| | CMSIS-Core |
| +-----------------------------------------------+
+-------------------------------------------------+
| 硬件 (ARM Cortex-M 核 + 外设) |
+-------------------------------------------------+
最底层为硬件,包括 Cortex-M 内核和芯片厂商设计的外设。中间层为 CMSIS,他与内核和底层硬件交互。最上层为用户应用和第三方中间件,他们通过调用 CMSIS 的 API 访问硬件,从而和具体的芯片型号解耦。
CMSIS 包含很多部分:
- CMSIS Core,提供与 Cortex-M0/3/4、SCx00 处理器与外围寄存器之间的接口;
- CMSIS DSP,包含以定点和单精度浮点实现的多种函数的 DSP 库;
- CMSIS RTOS,用于线程控制、资源和时间管理的 RTOS 的标准化编程接口;
- CMSIS SVD,用于包含完整微处理器系统的程序员视图的系统视图描述 XML 文件;
- CMSIS NN,是一组高校的神经网络(NPU)内核;
- CMSIS Driver 接口适用于很多 MCU 系列;
- CMSIS DAP,是 Cortex 调试访问端口(DAP)的标准化接口;
- 等等。
现仅对 CMSIS-Core 做介绍。
CMSIS-Core(Cortex-M)组件为 Arm Cortex-M 系列芯片提供了基本的运行时系统功能,同时让用户能够访问处理器核心及各种外设。具体来说,它定义了:
- 针对 Cortex-M 处理器的硬件抽象层(HAL)为 SysTick、NVIC、系统控制块寄存器、MPU 寄存器、FPU 寄存器以及核心访问功能提供了标准化的定义。
- 系统异常名称,用于在处理系统异常时避免兼容性问题。
- 组织头文件的方法,包括针对各种设备特有的中断所制定的命名规则。
- 各 MCU 制造商所采用的系统初始化方法。例如,标准化的
SystemInit()函数对于配置设备的时钟系统至关重要。 - 用于生成那些标准 C 函数所不支持的各种 CPU 指令的内置函数。
- 一个用于确定系统时钟频率的变量,该变量有助于简化 SysTick 定时器的配置过程。
对于每一个例程,其包含了以下内容:
- 启动文件
startup_<device>.s,即启动文件。参考 Cortex-M 启动文件。 - 系统配置文件
system<device>.c和system_<device>.h,如system_stm32f4xx.c。其中存储了设备的各种配置信息。 - 设备头文件
<device>.h,提供了对处理器核心和所有外设的访问接口,如stm32f411xe.h。
在程序复位时,其会执行启动文件,该文件会在 Reset_Handler 中调用 System_Init()。系统配置文件负责处理器的时钟设置。
设备头文件中提供了对以下特定设备相关的功能的访问接口:
- 外设接口。其为外设提供了标准化的寄存器布局。
- NVIC 接口。提供了标准符号和函数。
- CPU 指令内置函数。用于调用 CPU 的特殊指令,比如睡眠模式或 NOP。
- Systick Timer(SYSTICK),用于配置和启动周期性定时器中断。
HAL 库
在 CMSIS 的基础上,芯片厂商通常还会提供 HAL 库。这些库通过调用 CMSIS 接口来提供更加易于使用的驱动 API。
如,对于 GPIO 的操作,如果不使用 CMSIS/HAL 库,其需要直接操作寄存器:
*(volatile uint32_t*)0x48000014 |= (1 << 5);
利用 CMSIS,则可以写成:
GPIOA->ODR |= (1 << 5);
可以看到,其提供了 GPIOA、GPIO_TypeDef、__IO 等。但是依然在直接操作寄存器 ODR。
如果利用 ST 公司提供的 HAL 库,则可以写成:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
可以看到其提供了更好的封装。